package com.nh.glory.ware.security.authentication.jwt;


import com.nh.glory.core.constant.Constants;
import com.nh.glory.ware.security.model.LoginUserDetails;
import io.jsonwebtoken.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Date;
import java.util.UUID;

@Component
public class JwtFactory {

    private final JwtSettings settings;

    @Autowired
    public JwtFactory(JwtSettings settings) {
        this.settings = settings;
    }

    /**
     * Factory method for issuing new JWT Tokens.
     */
    public JwtToken createToken(LoginUserDetails details) {

        if (StringUtils.isEmpty(details.getUsername()))
            throw new IllegalArgumentException("Cannot create token without username");

        if (details.getAuthorities() == null || details.getAuthorities().isEmpty())
            throw new IllegalArgumentException("User doesn't have any privilege");

        Claims claims = Jwts.claims().setSubject(details.getUsername());
        claims.put(Constants.JWT_CLAIM_USERID, details.getId());
        LocalDateTime currentTime = LocalDateTime.now();

        JwtBuilder builder = Jwts.builder();
        builder.setHeaderParam(JwsHeader.TYPE, JwsHeader.JWT_TYPE);
        builder.setClaims(claims);
        builder.setId(UUID.randomUUID().toString());
        builder.setIssuer(settings.getIssuer());
        builder.setIssuedAt(Date.from(currentTime.atZone(ZoneId.systemDefault()).toInstant()));
        builder.setExpiration(Date.from(currentTime.plusSeconds(settings.getExpiration()).atZone(ZoneId.systemDefault()).toInstant()));
        builder.signWith(SignatureAlgorithm.HS256, settings.getSecret());
        return new JwtToken(builder.compact(), claims);
    }

    public Boolean validateToken(JwtToken jwt) {
        Jws<Claims> claims = getClaims(jwt.getToken(), settings.getSecret());

        final Long userId = claims.getBody().get(Constants.JWT_CLAIM_USERID, Long.class);
        final String username = claims.getBody().getSubject();
        final Date expiration = claims.getBody().getExpiration();

        return (userId != null && !StringUtils.isEmpty(username) && !expiration.before(new Date()));
    }

    public String getUsername(JwtToken jwt) {
        return getClaims(jwt.getToken(), settings.getSecret()).getBody().getSubject();
    }


    private Jws<Claims> getClaims(String jwtToken, String secret) {
        try {
            return Jwts.parser().setSigningKey(secret).parseClaimsJws(jwtToken);
        } catch (UnsupportedJwtException | MalformedJwtException | IllegalArgumentException | SignatureException e) {
            throw new BadCredentialsException("Authentication failed, token invalid");
        } catch (ExpiredJwtException ex) {
            throw new BadCredentialsException("Authentication failed, token expired");
        }
    }

    private Boolean isExpired(JwtToken jwt) {
        final Date expiration = getExpiration(jwt);
        return expiration.before(new Date());
    }

    private Date getExpiration(JwtToken jwt) {
        return getClaims(jwt.getToken(), settings.getSecret()).getBody().getExpiration();
    }

    private Long getUserId(JwtToken jwt) {
        return getClaims(jwt.getToken(), settings.getSecret()).getBody().get(Constants.JWT_CLAIM_USERID, Long.class);
    }

}
